﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace bxml.betterxml.xelement
{
    public class XAttributes : IEnumerable<String>
    {
        public class Attribute
        {

            private String qname;
            private String localName;
            private String uri;
            private String value;

            public Attribute(String name, String value)
                : this(name, name, "", value)
            {

            }

            public Attribute(String qname, String localName, String uri, String value)
            {
                this.qname = qname;
                this.localName = localName;
                this.uri = uri;
                this.value = value;
            }

            public String getQName()
            {
                return qname;
            }

            public String getURI()
            {
                return uri;
            }

            public String getLocalName()
            {
                return localName;
            }

            public String getValue()
            {
                return value;
            }

        }

        private Dictionary<String, Attribute> qnameToAttribute = new Dictionary<String, Attribute>();
        private Dictionary<String, Attribute> uriLocalToAttribute = new Dictionary<String, Attribute>();

        public XAttributes() { }

        /**
         * Set an attribute name to the given value. If the attribute already exists, the 
         * value will be overwritten. If the attribute does not exist, it will be created 
         * and given the supplied value.
         * @param qname  String. The attribute's qualified name.
         * @param value  String. The value that the attribute should be set to.
         */
        public void setAttribute(String name, String value)
        {
            setAttribute(new Attribute(name, value));
        }

        /**
         * Set an attribute name to the given value. If the attribute already exists, the 
         * value will be overwritten. If the attribute does not exist, it will be created 
         * and given the supplied value.
         * @param qname  String. The attribute's qualified name.
         * @param localName String. The attribute's local name.
         * @param uri String. The attribute's uri.
         * @param value  String. The value that the attribute should be set to.
         */
        public void setAttribute(String qName, String localName, String uri, String value)
        {
            setAttribute(new Attribute(qName, localName, uri, value));
        }

        private void setAttribute(Attribute attr)
        {
            qnameToAttribute.Add(attr.getQName(), attr);
            String localKey = GetUriLocalKey(attr.getURI(), attr.getLocalName());
            uriLocalToAttribute.Add(localKey, attr);
        }


        /**
         * Checks if an attribute with the supplied name exists in the instance of XAttributes.
         * @param qName  String. Quanified name of the attribute to look for in instance of XAttributes.
         * @return  boolean. True if the supplied name exists. False if it doesn't.
         */
        public bool containsAttribute(String qName)
        {
            return qnameToAttribute.ContainsKey(qName);
        }

        /**
         * Checks if an attribute with the supplied name exists in the instance of XAttributes.
         * @param uri.  String. The uri of the attribute. If there is no uri, this can be an empty string or null.
         * @param localName  String. Local name of the attribute to look for in instance of XAttributes.
         * @return  boolean. True if the supplied name exists. False if it doesn't.
         */
        public bool containsAttribute(String uri, String localName)
        {
            String localKey = GetUriLocalKey((uri == null ? "" : uri), localName);
            return uriLocalToAttribute.ContainsKey(localKey);
        }


        /**
         * Checks if all the attributes names found in the supplied lists exist in this
         * instance of XAttributes
         * @param qnames A list of strings, each one an attribute's qualified name
         * @return boolean. True if they all exist. False if at least one does not.
         */
        public bool containsAttributes(IEnumerable<String> qnames)
        {
            foreach (String qname in qnames)
            {
                if (!containsAttribute(qname))
                {
                    return false;
                }
            }
            return true;
        }

        /**
         * Gets value associated with the supplied attribute name.
         * @param name  String. Name of the attribute to get value for in instance of XAttributes.
         * @return  String. The value associated with the supplied attribute name.
         */
        public String getAttributeValue(String qName)
        {
            return getAttributeValue(qnameToAttribute[qName]);
        }

        /**
         * Gets value associated with the supplied attribute uri and localname.
         * @param uri  String. URI of the attribute to get value for in instance of XAttributes.
         * @param localName String. local name of the attribute to get value for in instance of XAttributes.
         * @return  String. The value associated with the supplied attribute name.
         */
        public String getAttributeValue(String uri, String localName)
        {
            String uriLocalKey = GetUriLocalKey(uri, localName);
            return getAttributeValue(uriLocalToAttribute[uriLocalKey]);
        }

        private String getAttributeValue(Attribute attr)
        {
            if (attr == null) return null;
            return attr.getValue();
        }

        /**
         * Removes the attribute from the Map of attributes
         * that has the same name as the supplied attribute name.
         * @param aName  String. Qualified name of attribute to be removed.
         */
        public void removeAttribute(String qname)
        {
            removeAttribute(qnameToAttribute[qname]);
        }

        /**
         * Removes the attribute from the Map of attributes
         * that has the same name as the supplied attribute name.
         * @param uri  String. URI of attribute to be removed.
         * @param localName  String. Local Name of attribute to be removed.
         */
        public void removeAttribute(String uri, String localName)
        {
            String uriLocalKey = GetUriLocalKey(uri, localName);
            removeAttribute(uriLocalToAttribute[uriLocalKey]);
        }


        private void removeAttribute(Attribute attr)
        {
            if (attr == null) return;
            qnameToAttribute.Remove(attr.getQName());
            String uriLocalKey = GetUriLocalKey(attr.getURI(), attr.getLocalName());
            uriLocalToAttribute.Remove(uriLocalKey);
        }

        /**
         * Gets the number of attribute name and value pairs in the instance of XAttributes
         * @return  int. The number of attribute name and value pairs in the instance of XAttributes.
         */
        public int size()
        {
            return qnameToAttribute.Count();
        }


        private static String GetUriLocalKey(String uri, String local)
        {
            return (uri == null ? "" : uri) + ":" + local;
        }

        /**
         * Generates and returns a String of all the attribute name and value pairs formatted for XML.<br>
         * For example: attribute1:value1, attribute2:value2 will be formatted as "attribute1='value1' attribute2='value2'"<br>
         * (sans double quotes)
         * @return  String. The attribute name and value pairs properly formatted for XML.
         */
        public String toXML()
        {
            if (size() == 0) return "";

            StringBuilder buf = new StringBuilder();
            foreach (Attribute attr in qnameToAttribute.Values)
            {
                buf.Append(attr.getQName());
                buf.Append("='");
                buf.Append(attr.getValue());
                buf.Append("' ");
            }

            return buf.ToString().Trim();	//trim gets rid of trailing white space after last attribute
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }

        public IEnumerator<string> GetEnumerator()
        {
            foreach (var pair in qnameToAttribute)
            {
                yield return pair.Key;
            }
        }

    }
}
